#coding=utf-8
'''這是一個HTML編輯器，使用QT4，WebKit建立'''

import os
import time
import platform
import fnmatch
import shutil
import functools
import re
import string
import shelve
import sqlite3

from PyQt4.QtGui import *
from PyQt4.QtCore import *
from PyQt4.QtWebKit import *

import lonote.path as path
import lonote.shuext as shuext
import lonote.webutils as webutils
import lonote.js as js

templateDir = path.fix.templateDir


class LoPerviewer(QWebView):
    '''把Context Menu關掉的QWebView'''
    def contextMenuEvent(self, ev):
        pass



class LoWebView(QWebView):
    '''供LoEditor用的QWebView'''

    imageUrlsDrop = pyqtSignal(list) #image連結被拖放之後的結果

    def pastePlain(self):
        '''貼上純文字'''
        self.page().triggerAction(QWebPage.PasteAndMatchStyle)

    def copyPlain(self):
        '''如果selectText中有東西，複製。否則為空操作。'''
        if self.page().selectedText():
            clipboard = QApplication.clipboard()
            clipboard.setText(self.page().selectedText())

    def cutPlain(self):
        '''如果selectText中有東西，剪下。否則為空操作。'''
        if self.page().selectedText():
            clipboard = QApplication.clipboard()
            text = self.page().selectedText()
            self.page().triggerAction(QWebPage.Cut)
            clipboard.setText(text)

    def copyLink(self):
        '''如果selectText中有東西，剪下。否則為空操作。'''
        clipboard = QApplication.clipboard()
        self.page().triggerAction(QWebPage.CopyLinkToClipboard)

    def __init__(self, parent = None):
        '''增加Shortcut'''
        super().__init__(parent)

        #快速鍵設定
        #貼上純文字
        shortPastePlain = QShortcut('Ctrl+Shift+V', self, context = Qt.WidgetShortcut)
        shortPastePlain.activated.connect(self.pastePlain)
        #複製純文字
        shortCopyPlain = QShortcut('Ctrl+Shift+C', self, context = Qt.WidgetShortcut)
        shortCopyPlain.activated.connect(self.copyPlain)
        #剪下純文字
        shortCutPlain = QShortcut('Ctrl+Shift+X', self, context = Qt.WidgetShortcut)
        shortCutPlain.activated.connect(self.cutPlain)

        #建立contextMenu
        self.createContextMenu()

    def createContextMenu(self):
        '''建立ContextMenu'''
        self.menu = QMenu(self) #總清單
        #shorter = functools.partial(textShorter, length = 15)
        #建立action
        #貼上純文字
        #text = '\t' + shorter(clipboard.text())
        act = QAction(_('Paste plain text') + '\tCtrl+Shift+V', self)
        act.setStatusTip(_('Paste plain text (Ctrl+Shift+V)'))
        act.triggered.connect(self.pastePlain)
        self.menu.addAction(act)
        #複製純文字
        #text = '\t' + shorter(self.page().selectedText())
        act = QAction(_('Copy plain text') + '\tCtrl+Shift+C', self)
        act.setStatusTip(_('Copy plain text (Ctrl+Shift+C)'))
        act.triggered.connect(self.copyPlain)
        self.menu.addAction(act)
        #剪下純文字
        act = QAction(_('Cut plain text') + '\tCtrl+Shift+X', self)
        act.setStatusTip(_('Cut plain text (Ctrl+Shift+X)'))
        act.triggered.connect(self.cutPlain)
        self.menu.addAction(act)
        self.menu.addSeparator()
        ##undo
        #act = QAction(_('Undo'), menu)
        #act.triggered.connect(functools.partial(self.page().triggerAction, QWebPage.Undo))
        #menu.addAction(act)
        #複製連結到剪貼簿
        act = QAction(_('Copy link to clipboard'), self)
        act.triggered.connect(self.copyLink)
        self.menu.addAction(act)

    def contextMenuEvent(self, ev):
        '''重設右鍵選單'''
        #def textShorter(string, length):
        #    '''把文字縮短到指定的長度，'''
        #    if len(string) > length:
        #        return string[:length] + _('...')
        #    else:
        #        return string

        #秀出選單
        self.menu.exec_(QCursor.pos())

    def dragMoveEvent(self, event):
        super().dragMoveEvent(event)

    def dropEvent(self, event):
        '''用拖拉來插入圖檔'''
        def isSupportImage(path):
            '''檢查副檔名看看是否是支援的圖檔'''
            valid = ['.png', '.gif', '.jpg', '.jpeg', '.svg', '.bmp']
            base,ext = os.path.splitext(path)
            if ext in valid:
                return True
            else:
                return False
        data = event.mimeData()
        #如果從外面拖進來且含有url，則執行插入圖檔任務
        #print('s:', event.source())
        #print('f:', data.formats())
        #print('h:', data.html())
        #print('u:', data.urls())
        #print('t:', data.text())
        if event.source() is None and data.hasUrls():
            #解析urls，條件為「存在」的「本地圖片」
            localUrls = [url.toLocalFile() for url in data.urls()
                    if url.isLocalFile() and
                    isSupportImage(url.toLocalFile())]
            #分析網路圖片
            webUrls = [url.toString() for url in data.urls()
                    if not url.isLocalFile() and
                    isSupportImage(url.path())]
                    #FIXME: 有些網路圖片沒有副檔名，因此抓不到
                    #       或許該移除這裡的偵測
            #print('w:',webUrls)
            for wUrl in webUrls:
                lUrl = webutils.downloadWebResourse(wUrl) #抓取到lUrl
                if lUrl:
                    localUrls.append(lUrl)
            #print('l:',localUrls)
            #將urls扔去插入圖片用
            if localUrls != []:
                #先變更游標位置
                JS = js.setCaretByPos.replace(
                        '{$posx}', str(event.pos().x()))
                JS = JS.replace('{$posy}', str(event.pos().y()))
                self.page().mainFrame().evaluateJavaScript(JS)
                #執行插入指令
                self.imageUrlsDrop.emit(localUrls)
                return
        super().dropEvent(event)



class LoEditor(QWidget):
    '''這是一個Qt Html編輯器'''

    lowikiClickedSignal = pyqtSignal(int)

#初始化

    def __init__(self, parent = None):
        super(QWidget, self).__init__(parent)

        #面板物件初始化
        self.noteArea = LoWebView()
        self.__initNoteArea()
        self.__editToolbar = QToolBar()
        self.__findbar = QToolBar()
        self.__findEdit = QLineEdit()
        #self.replaceEdit = QLineEdit()

        #鋪設主版面
        self.__initLayout()

        #全域page設定初始化
        self.__initGlobalWebSetting()

        #產生並配置面板按鈕小工具
        self.__initEditFun()
        self.__initTool()

        #（使用者看不見的）內部工具物件初始化
        self.__doc = QTextDocument() # 用來產生plainText的物件

        #參數初始化

        ### 各種格式設定 ###
        self.__fontfamily_st = 'wqy-microhei' # 編輯器標準字型
        self.__fontsize_multiplier = 1.0 # 文字放大率
        self.DATE_FORMAT = '%Y %B %d' # 日期插入格式
        self.TIME_FORMAT = '%p %H:%M' # 時間插入格式
        self.IMG_FORMAT = '<p>{$IMAGE}</p><br>' # 圖片插入格式
        self.IMG_FORMAT_WIDTH = 500 # 圖片插入寬度
        self.IMG_FORMAT_INSERTRESIZE_ENABLE = True # 圖片插入實體縮放
        # 圖片插入品質，範圍為-1~100，100品質最好，0最差，-1是預設
        self.IMG_FORMAT_INSERTRESIZE_QUALITY = -1

        # 初始化 cache
        self.resetCache()

    def resetCache(self):
        '''重設cache為初值'''
        self.__pageuuid = None
        self.__cache = {} #用uuid索引(page, iFramesUrl list)的字典
        self.pageemb = None #當前的embeddedpath

    def __initNoteArea(self):
        self.noteArea.setRenderHint(QPainter.Antialiasing, True)
        self.noteArea.setRenderHint(QPainter.TextAntialiasing, True)
        self.noteArea.setRenderHint(QPainter.SmoothPixmapTransform, True)
        self.noteArea.setRenderHint(QPainter.HighQualityAntialiasing, True)
        #圖片連結被拖入後進行複製
        self.noteArea.imageUrlsDrop.connect(self._inimg)

    def __initLayout(self):
        '''初始化noteArea和editToolbar等Layout'''
        #======__findbar的設定======
        label = QLabel(_('Find: '))
        self.__findEdit = QLineEdit()
        self.__findEdit.setMaximumWidth(350)
        self.__findEdit.setStatusTip(_('This is a search bar. Please input some word you want to find.'))
        #__findbar快速鍵召喚＼反召喚
        self.shortcut_findbarShow = QShortcut(QKeySequence("Ctrl+F"), self.noteArea)
        self.shortcut_findbarShow.activated.connect(self.__findbarShow)
        self.shortcut_findbarHide = QShortcut(QKeySequence("ESC"), self.__findbar)
        self.shortcut_findbarHide.activated.connect(self.__findbarHide)
        #關閉按鈕
        closeButton = QPushButton(QIcon(path.icons.close), '', self.__findbar)
        closeButton.setStatusTip(_('Close The Search Bar (Esc)'))
        closeButton.pressed.connect(self.__findbarHide)
        closeButton.setFlat(True)
        #下一個按鈕
        nextButton = QPushButton(QIcon(path.icons.yes), _('Next＞'), self.__findbar)
        nextButton.setStatusTip(_('Search the next match word. (Enter)'))
        nextButton.pressed.connect(self.findNext)
        #前一個
        beforeButton = QPushButton(QIcon(path.icons.before), _('＜Before'), self.__findbar)
        beforeButton.setStatusTip(_('Search the match word before here.'))
        beforeButton.pressed.connect(self.findBefore)
        #__findEdit connect
        self.__findEdit.textChanged.connect(self.findAll)
        self.__findEdit.returnPressed.connect(self.findNext)
        #鋪設__findbarLayout
        self.__findbar.addWidget(closeButton)
        self.__findbar.addWidget(label)
        self.__findbar.addWidget(self.__findEdit)
        self.__findbar.addWidget(beforeButton)
        self.__findbar.addWidget(nextButton)
        self.__findbar.hide()

        #======終於把__findbar設定好了！======

        #Layout設定（由小到大）
        mainLayout = QVBoxLayout()
        mainLayout.addWidget(self.noteArea)
        mainLayout.addWidget(self.__findbar)
        mainLayout.addWidget(self.__editToolbar)

        self.setLayout(mainLayout)

    def __initEditFun(self):
        '''設定所有編輯按鈕、選單、快捷鍵的功能、圖示與說明，但不包括安排顯示位置'''
        #黑體字:bold
        self.bold = QAction(QIcon(path.icons.bold), _('Bold'), self)
        self.bold.setShortcut('Ctrl+B')
        self.bold.setStatusTip(_('toggle bold (Ctrl+B)'))
        self.connect(self.bold, SIGNAL('triggered()'), SLOT('_bold()'))
        #斜體字:italic
        self.italic = QAction(QIcon(path.icons.italic), _('Italic'), self)
        self.italic.setShortcut('Ctrl+I')
        self.italic.setStatusTip(_('toggle italic (Ctrl+I)'))
        self.connect(self.italic, SIGNAL('triggered()'), SLOT('_italic()'))
        #底線字:underline
        self.underline = QAction(QIcon(path.icons.underline), _('Underline'), self)
        self.underline.setShortcut('Ctrl+U')
        self.underline.setStatusTip(_('toggle underline (Ctrl+U)'))
        self.connect(self.underline, SIGNAL('triggered()'), SLOT('_underline()'))
        #畫線字:strike
        self.strike = QAction(QIcon(path.icons.strike), _('Strike'), self)
        self.strike.setShortcut('Ctrl+S')
        self.strike.setStatusTip(_('toggle strike (Ctrl+S)'))
        self.strike.triggered.connect(self._strike)
        #上標:superscript
        self.superscript = QAction(QIcon(path.icons.sup), _('Superscript'), self)
        self.superscript.setStatusTip(_('toggle superscript (Alt+K)'))
        self.superscript.setShortcut('Alt+K')
        self.connect(self.superscript, SIGNAL('triggered()'), SLOT('_superscript()'))
        #下標:subscript
        self.subscript = QAction(QIcon(path.icons.sub), _('Subscript'), self)
        self.subscript.setStatusTip(_('toggle subscript (Alt+J)'))
        self.subscript.setShortcut('Alt+J')
        self.connect(self.subscript, SIGNAL('triggered()'), SLOT('_subscript()'))
        #靠左對齊:justifyLeft
        self.justifyLeft = QAction(QIcon(path.icons.left), _('Left'), self)
        self.justifyLeft.setStatusTip(_('Align to left (Alt+,)'))
        self.justifyLeft.setShortcut('Alt+,')
        self.connect(self.justifyLeft, SIGNAL('triggered()'), SLOT('_justifyLeft()'))
        #置中對齊:justifyCenter
        self.justifyCenter = QAction(QIcon(path.icons.center), _('Center'), self)
        self.justifyCenter.setStatusTip(_('Align to center (Alt+M)'))
        self.justifyCenter.setShortcut('Alt+M')
        self.connect(self.justifyCenter, SIGNAL('triggered()'), SLOT('_justifyCenter()'))
        #靠右對齊:justifyRight
        self.justifyRight = QAction(QIcon(path.icons.right), _('Right'), self)
        self.justifyRight.setStatusTip(_('Align to right (Alt+.)'))
        self.justifyRight.setShortcut('Alt+.')
        self.connect(self.justifyRight, SIGNAL('triggered()'), SLOT('_justifyRight()'))
        #縮排:indent
        self.indent = QAction(QIcon(path.icons.indent), _('Indent'), self)
        self.indent.setStatusTip(_('indent (Alt+])'))
        self.indent.setShortcut('Alt+]')
        self.connect(self.indent, SIGNAL('triggered()'), SLOT('_indent()'))
        #反縮排:outdent
        self.outdent = QAction(QIcon(path.icons.outdent), _('Outdent'), self)
        self.outdent.setStatusTip(_('outdent (Alt+[)'))
        self.outdent.setShortcut('Alt+[')
        self.connect(self.outdent, SIGNAL('triggered()'), SLOT('_outdent()'))
        #有序列表:ordlist
        self.ordlist = QAction(QIcon(path.icons.ordlist), _('Ordered list'), self)
        self.ordlist.setStatusTip(_('ordered list (Ctrl+O)'))
        self.ordlist.setShortcut('Ctrl+O')
        self.connect(self.ordlist, SIGNAL('triggered()'), SLOT('_ordlist()'))
        #無序列表:bullist
        self.bullist = QAction(QIcon(path.icons.bullist), _('Bullet list'), self)
        self.bullist.setStatusTip(_('bullet list (Ctrl+P)'))
        self.bullist.setShortcut('Ctrl+P')
        self.connect(self.bullist, SIGNAL('triggered()'), SLOT('_bullist()'))
        #前景色:fgcolor
        self.fgcolor = QAction(QIcon(path.icons.forecol), _('Text color'), self)
        self.fgcolor.setStatusTip(_('Select a text color. (Ctrl+E)'))
        self.fgcolor.setShortcut('Ctrl+E')
        self.fgcolor.triggered.connect(self._fgcolor)
        #背景色:bgcolor
        self.bgcolor = QAction(QIcon(path.icons.bgcol), _('Background color'), self)
        self.bgcolor.setStatusTip(_('Select a background color. (Ctrl+D)'))
        self.bgcolor.setShortcut('Ctrl+D')
        self.bgcolor.triggered.connect(self._bgcolor)
        #標籤
        self.headingBox = QComboBox(self)
        self.headingBox.addItems([_('--Tags--'),_('Paragraph'),_('Heading1'),_('Heading2'),_('Heading3'),_('Heading4'),_('Heading5'),_('Heading6'),_('Blockquote'),_('code'),_('address')])
        self.headingBox.setStatusTip(_('Assign line format. (Alt+` ~ Alt+9)'))
        self.headingBox.activated[int].connect(self._headingChanged)
        #字體大小
        self.fontsize = QComboBox(self)
        self.fontsize.addItems([_('Fontsize'),'１','２',_('Standard'),'４','５','６','７'])
        self.fontsize.setStatusTip(_('Assign font size.'))
        self.fontsize.activated[int].connect(self._fontsizeChanged)
        #插入圖片:inimg
        self.inimg = QAction(QIcon(path.icons.inimg), _('Insert images'), self)
        self.inimg.setStatusTip(_('Insert images on your cursor position. (Alt+I)'))
        self.inimg.setShortcut('Alt+I')
        self.inimg.triggered.connect(self._inimg)
        #加入超聯結:link
        self.link = QAction(QIcon(path.icons.link), _('Hyperlink'), self)
        self.link.setStatusTip(_('Convert to hyperlink (Ctrl+L)    ☆Hint: you can use "TEXT@URL" format.'))
        self.link.setShortcut('Ctrl+L')
        self.link.triggered.connect(self._link)
        #移除超聯結:unlink
        self.unlink = QAction(QIcon(path.icons.unlink), _('unlink'), self)
        self.unlink.setStatusTip(_('Remove selected hyperlink (Ctrl+Shift+K)'))
        self.unlink.setShortcut('Ctrl+Shift+K')
        self.unlink.triggered.connect(self._unlink)
        #去除格式:noformat
        self.noformat = QAction(QIcon(path.icons.noformat), _('Remove Format'), self)
        self.noformat.setStatusTip(_('Remove text format (Ctrl+K)'))
        self.noformat.setShortcut('Ctrl+K')
        self.noformat.triggered.connect(self._noformat)
        #插入HTML原始碼：
        self.inhtml = QAction(QIcon(path.icons.inhtml), _('Insert HTML source'), self)
        self.inhtml.setStatusTip(_('Insert a HTML source code on your cursor position. (Ctrl+Alt+I)'))
        self.inhtml.setShortcut('Ctrl+Alt+I')
        self.inhtml.triggered.connect(self._inhtml)
        #插入日期
        self.indate = QAction(QIcon(path.icons.indate), _('Insert Date'), self)
        self.indate.setStatusTip(_('Insert current date on your cursor position (Alt+D)'))
        self.indate.setShortcut('Alt+D')
        self.indate.triggered.connect(self._indate)
        #插入時間
        self.intime = QAction(QIcon(path.icons.intime), _('Insert time'), self)
        self.intime.setStatusTip(_('Insert current time on your cursor position (Alt+T)'))
        self.intime.setShortcut('Alt+T')
        self.intime.triggered.connect(self._intime)
        #插入快速模板
        self.intemplate = QAction(QIcon(path.icons.intemplate), _('Quick template'), self)
        self.intemplate.setStatusTip(_('Insert a quick template on current cursor position (Alt+Q)    ☆Hint: The quick templates be defined in "{0}"').format(templateDir))
        self.intemplate.setShortcut('Alt+Q')
        self.intemplate.triggered.connect(self._intemplate)

        #連結被敲擊後的反應
        self.noteArea.linkClicked[QUrl].connect(self._linkClicked)

        #快捷鍵
        self.shortcut_P = QShortcut(QKeySequence("Alt+`"), self.noteArea)
        self.shortcut_P.activated.connect(self.fbP)
        self.shortcut_P2 = QShortcut(QKeySequence("Alt+0"), self.noteArea)
        self.shortcut_P2.activated.connect(self.fbP)

        self.shortcut_H1 = QShortcut(QKeySequence("Alt+1"), self.noteArea)
        self.shortcut_H1.activated.connect(self.fbH1)
        self.shortcut_H2 = QShortcut(QKeySequence("Alt+2"), self.noteArea)
        self.shortcut_H2.activated.connect(self.fbH2)
        self.shortcut_H3 = QShortcut(QKeySequence("Alt+3"), self.noteArea)
        self.shortcut_H3.activated.connect(self.fbH3)
        self.shortcut_H4 = QShortcut(QKeySequence("Alt+4"), self.noteArea)
        self.shortcut_H4.activated.connect(self.fbH4)
        self.shortcut_H5 = QShortcut(QKeySequence("Alt+5"), self.noteArea)
        self.shortcut_H5.activated.connect(self.fbH5)
        self.shortcut_H6 = QShortcut(QKeySequence("Alt+6"), self.noteArea)
        self.shortcut_H6.activated.connect(self.fbH6)

        self.shortcut_Bq = QShortcut(QKeySequence("Alt+7"), self.noteArea)
        self.shortcut_Bq.activated.connect(self.fbBq)
        self.shortcut_Pre = QShortcut(QKeySequence("Alt+8"), self.noteArea)
        self.shortcut_Pre.activated.connect(self.fbPre)
        self.shortcut_Addr = QShortcut(QKeySequence("Alt+9"), self.noteArea)
        self.shortcut_Addr.activated.connect(self.fbAddr)

    def __initTool(self):
        '''擺放工具列'''
        self.__editToolbar.addWidget(self.headingBox)
        self.__editToolbar.addWidget(self.fontsize)
        self.__editToolbar.addAction(self.noformat)
        self.__editToolbar.addAction(self.bold)
        self.__editToolbar.addAction(self.italic)
        self.__editToolbar.addAction(self.underline)
        self.__editToolbar.addAction(self.strike)
        self.__editToolbar.addSeparator()
        self.__editToolbar.addAction(self.ordlist)
        self.__editToolbar.addAction(self.bullist)
        self.__editToolbar.addSeparator()
        self.__editToolbar.addAction(self.inimg)
        self.__editToolbar.addAction(self.inhtml)
        self.__editToolbar.addAction(self.intemplate)
        self.__editToolbar.addSeparator()
        self.__editToolbar.addAction(self.indate)
        self.__editToolbar.addAction(self.intime)
        self.__editToolbar.addSeparator()
        self.__editToolbar.addAction(self.link)
        self.__editToolbar.addAction(self.unlink)
        self.__editToolbar.addSeparator()
        self.__editToolbar.addAction(self.outdent)
        self.__editToolbar.addAction(self.indent)
        self.__editToolbar.addSeparator()
        self.__editToolbar.addAction(self.fgcolor)
        self.__editToolbar.addAction(self.bgcolor)
        self.__editToolbar.addSeparator()
        self.__editToolbar.addAction(self.justifyLeft)
        self.__editToolbar.addAction(self.justifyCenter)
        self.__editToolbar.addAction(self.justifyRight)
        self.__editToolbar.addAction(self.superscript)
        self.__editToolbar.addAction(self.subscript)

    def __initGlobalWebSetting(self):
        '''設定預設的顯示相關參數'''
        webSetting = QWebSettings.globalSettings()
        #設定其他相關
        webSetting.setAttribute(QWebSettings.PluginsEnabled, True)
        webSetting.setAttribute(QWebSettings.AutoLoadImages, True)
        webSetting.setAttribute(QWebSettings.JavascriptEnabled, True)
        webSetting.setAttribute(QWebSettings.JavaEnabled, True)
        webSetting.setAttribute(
                QWebSettings.JavascriptCanAccessClipboard, True)
        webSetting.setAttribute(
                QWebSettings.JavascriptCanOpenWindows, True)
        #webSetting.setWebGraphic(
        #        QWebSettings.MissingImageGraphic, QPixmap(path.icons.error))

#頁面相關操作

    def inject(self, htmlstr, id = None, embPath = ''):
        '''將html原始碼連同新的路徑組態一起注入

        htmlstr = 要讓editor顯示的內容
        id = 頁面的id，主要影響cache操作。
        embPath = 所有內嵌元素的基礎路徑，也是圖片插入功能的目標'''

        if id is None: # FIXME: 臨時id2uuid轉譯，事後請去掉
            uuid = None
        else:
            uuid = self.dbcur.execute('select uuid from notes where id = ?',
                [id,]).fetchone()[0]

        # 以下不會再出現 id 只有 uuid

        def needNewPage(uuid, htmlstr):
            '''依據 uuid 偵測 cache 中是否有現存的頁面 cache

            需要新頁的話會回傳True，否則回傳False'''
            if uuid is None:
                return True
            #檢查uuid有沒有在cache裡，有就用cache的內容。
            if uuid in self.__cache:
                #如果資料不同，表示已經被不知道誰從哪裡複寫過了。放棄cache
                page, iframesUrl = self.__cache[uuid]
                if page.mainFrame().toHtml() != htmlstr:
                    self.__cache.pop(uuid)
                    return True
                else:
                    return False
            else:
                return True

        def genNewPage():
            '''產生一個空白的新頁面，並做好基本設定'''
            page = QWebPage() #生成新頁面
            page.setContentEditable(True) #可編輯
            #點擊反應
            page.setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
            page.setForwardUnsupportedContent(True)
            #連結被滑過後觸發到指定函式
            page.linkHovered.connect(self._showLink)
            return page

        # 更新前進行回收等工作
        if self.__pageuuid is not None:
            childFrames = self.noteArea.page().mainFrame().childFrames()
            #self.noteArea.page().setView(None) #可以避免掉一些 bug 但會造成另一些bug，囧啊…… FIXME!
            # 記錄 Url 並關閉所有 iframe
            iframesUrl = tuple(f.requestedUrl() for f in childFrames)
            for f in childFrames:
                f.setUrl(QUrl(''))
            # 寫入cache
            self.__cache[self.__pageuuid] = (
                    self.noteArea.page(),
                    iframesUrl)

        #看看需不需要新建一頁
        if needNewPage(uuid, htmlstr):
            page = genNewPage() #產生新page
            #寫入page內容
            QBaseUrl = QUrl.fromLocalFile(embPath + '/')
            page.mainFrame().setHtml(htmlstr, QBaseUrl)
        else: # 從 cache 中讀取資料
            page, iframesUrl = self.__cache.pop(uuid)
            # 重載 iframe
            index = 0
            for f in page.mainFrame().childFrames():
                f.setUrl(QUrl(iframesUrl[index]))
                index += 1

        #重載圖片操作 JS 碼
        page.mainFrame().evaluateJavaScript(js.imgSizeControl)

        #換頁
        self.noteArea.setPage(page)

        #字體與放大率
        self.__fontRefresh(page)
        self.__fontSizeMultiplier_Refresh()

        #後續設定
        self.__pageuuid = uuid
        self.pageemb = embPath

        #更新findAll高亮
        self.findAll()

    @property
    def csspath(self):
        '''當前頁面使用的 css 檔案路徑'''
        return self.noteArea.page().settings().userStyleSheetUrl()

    @csspath.setter
    def csspath(self, value):
        page = self.noteArea.page()
        page.settings().setUserStyleSheetUrl(QUrl())
        page.settings().setUserStyleSheetUrl(QUrl.fromLocalFile(value))

    @property
    def scrollpos(self):
        '''當前的頁面滾動位置'''
        return self.noteArea.page().mainFrame().scrollBarValue(Qt.Vertical)

    @scrollpos.setter
    def scrollpos(self, value):
        #基本滾頁
        page = self.noteArea.page()
        page.mainFrame().setScrollBarValue(
                Qt.Vertical, value)
        #延遲滾頁，等到loadFinish之後再滾，解決非同步載入的問題
        def loadFinish():
            page.mainFrame().setScrollBarValue(Qt.Vertical, value)
            #FIXME: 下面這行在KDE + openSUSE 12.1 下可能會讓pyqt拋出錯誤……
            if qVersion() >= '4.8': #試試看是不是 qVersion 不足造成的問題。
                page.loadFinished.disconnect()
        page.loadFinished.connect(loadFinish) #設定延遲滾頁

#附屬db相關操作

    @property
    def dbcur(self):
        '''資料庫游標

        本游標被用做與lowiki協作使用，程式沒有指定本游標一樣可以運作。'''
        try:
            return self.__dbcur
        except AttributeError:
            self.__dbcur = None
            return self.__dbcur

    @dbcur.setter
    def dbcur(self, value):
        if self.dbcur: # 如果游標原本就有值，先關閉再賦值
            try:
                self.__dbcur.close()
            except sqlite3.ProgrammingError: pass # 如果資料庫已經另行關閉
        self.__dbcur = value

#基本資料存讀相關操作

    @property
    def html(self):
        '''取出Html'''
        return self.noteArea.page().mainFrame().toHtml()

    @property
    def plain(self):
        '''取出純文字'''
        self.__doc.setHtml(self.noteArea.page().mainFrame().toHtml())
        return self.__doc.toPlainText()

    @property
    def fontfamily_st(self):
        '''standard font family'''
        return self.__fontfamily_st

    @fontfamily_st.setter
    def fontfamily_st(self, value):
        self.__fontfamily_st = value
        self.__fontRefresh()

    @property
    def fontsize_multiplier(self):
        '''standard font family'''
        return self.__fontsize_mulitplier

    @fontsize_multiplier.setter
    def fontsize_multiplier(self, value):
        self.__fontsize_multiplier = value
        self.__fontSizeMultiplier_Refresh()

    def getID(self):
        '''當前頁面的 uuid

        用來當作當前頁面的識別字用'''
        try:
            #FIXME: 從 uuid 轉換成 id 的暫時元件，完成重構後就去掉
            if self.__pageuuid is None:
                ans = None
            else:
                ans = self.dbcur.execute('select id from notes where uuid = ?',
                    [self.__pageuuid,]).fetchone()[0] #uuid 轉譯成 id
        # 刪掉現存頁面後瞬間，會因為 uuid 無法映射到 id 而從資料庫中 fetch 出 None，一旦 None 取 index ，就會導致 TypeError
        except (AttributeError, TypeError):
            self.__pageuuid = None #當前顯示中的__pageuuid
            ans = None
        return ans

## 外部操作介面的底層 ##

    def _showLink(self, link, title, textContent):
        '''連結被滑過時觸發的事件'''
        window = QApplication.activeWindow()
        #如果不是mainwindow就避開，否則會出錯
        #找不到直接調用mainwindow的方法Q_Q
        if not isinstance(window, QMainWindow):
            return
        #看看有沒有db資料，沒有就直接連結
        if not self.dbcur:
            window.statusBar().showMessage(link)
            return
        #判斷是不是lowiki連結，lowiki要另外處理
        qurl = QUrl(link)
        if qurl.scheme() == 'lowiki':
            uuid = qurl.path()
            getlen = 30
            answer = self.dbcur.execute('select name,contentplain from notes where uuid = ?', (uuid,)).fetchone()
            msg = _('Note: [ {0} ] ➜ {1}')
            if answer:
                msg = msg.format(answer[0], answer[1][:getlen])
                if len(answer[1]) > getlen:
                     msg += _('...')
            else:
                msg = msg.format('✖' ,_('PAGE DISAPPEAR!    ☆Hint: Maybe you should remove this link...'))
            window.statusBar().showMessage(msg)
        else:
            window.statusBar().showMessage(link)

#頁面狀態維護

    def __fontRefresh(self, page = None):
        '''設定的Font格式'''
        if page is None:
            page = self.noteArea.page()

        webSetting = page.settings()
        if self.__fontfamily_st:
            webSetting.setFontFamily(QWebSettings.StandardFont, self.__fontfamily_st)
        else:
            webSetting.resetFontFamily(QWebSettings.StandardFont)
        #webSetting.setFontFamily(QWebSettings.SerifFont, 'wqy-microhei')
        #webSetting.setFontFamily(QWebSettings.SansSerifFont, 'wqy-microhei')
        #webSetting.setFontFamily(QWebSettings.CursiveFont, 'wqy-microhei')
        #webSetting.setFontFamily(QWebSettings.FantasyFont, 'wqy-microhei')
        #webSetting.setFontFamily(QWebSettings.FixedFont, 'DroidSansMono')

    def __fontSizeMultiplier_Refresh(self):
        '''更新文字的放大率'''
        self.noteArea.setTextSizeMultiplier(self.__fontsize_multiplier)

#頁面搜尋功能

    @property
    def findtext(self):
        '''搜尋條棒中的文字'''
        return self.__findEdit.text()

    @findtext.setter
    def findtext(self, value):
        self.__findEdit.setText(value)

    def __findbarShow(self):
        '''把__findbar打開'''
        self.__findbar.show()
        self.__findEdit.setFocus()
        self.__findEdit.selectAll()
        self.findAll()

    def __findbarHide(self):
        '''把__findbar關上'''
        self.__findbar.hide()
        self.noteArea.findText('', QWebPage.HighlightAllOccurrences)
        self.noteArea.setFocus()

    def findAll(self, string = ''):
        '''將noteArea中所有指定的字串全部標示出來'''
        self.noteArea.findText('', QWebPage.HighlightAllOccurrences)
        #可見才要高亮
        if self.__findbar.isVisible():
            self.noteArea.findText(self.__findEdit.text(), QWebPage.HighlightAllOccurrences)

    def findNext(self, string = ''):
        '''尋找noteArea中指定的字串'''
        self.noteArea.findText(self.__findEdit.text(), QWebPage.FindWrapsAroundDocument)

    def findBefore(self):
        '''尋找noteArea中指定的字串'''
        self.noteArea.findText(self.__findEdit.text(), QWebPage.FindWrapsAroundDocument | QWebPage.FindBackward)

#特殊處理

    def _linkClicked(self, qurl):
        '''聯結被K之後的行動……'''
        print('hit')
        if qurl.scheme() == 'lowiki': #如果是lowiki，發出信號
            if self.dbcur:
                ans = self.dbcur.execute('select id from notes where uuid = ?', (qurl.path(),)).fetchone()[0]
                if ans:
                    self.lowikiClickedSignal.emit(ans)
        else:
            QDesktopServices.openUrl(qurl)

#編輯功能

## 去除格式 ##

    def _noformat(self):
        '''去除格式'''
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("removeFormat");')
        #self.noteArea.page().mainFrame().evaluateJavaScript(js.removeTag)

    def _unlink(self):
        '''移除連結'''
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("unlink");')

## 文字格式變更 ##

    @pyqtSlot()
    def _bold(self):
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("bold");')

    @pyqtSlot()
    def _italic(self):
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("italic");')

    @pyqtSlot()
    def _underline(self):
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("underline");')

    @pyqtSlot()
    def _strike(self):
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("strikethrough");')

    @pyqtSlot()
    def _superscript(self):
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("superscript");')

    @pyqtSlot()
    def _subscript(self):
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("subscript");')

## 段落格式變更 ##

#### formatblock變更 ####

    @pyqtSlot(int)
    def _headingChanged(self, index):
        if index == 0:
            pass
        elif index == 1:
            self.fbP()
        elif index == 2:
            self.fbH1()
        elif index == 3:
            self.fbH2()
        elif index == 4:
            self.fbH3()
        elif index == 5:
            self.fbH4()
        elif index == 6:
            self.fbH5()
        elif index == 7:
            self.fbH6()
        elif index == 8:
            self.fbBq()
        elif index == 9:
            self.fbPre()
        elif index == 10:
            self.fbAddr()
        self.headingBox.setCurrentIndex(0)
        self.noteArea.setFocus()

    def fbP(self):
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("formatblock", false,"p");')

    def fbH1(self):
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("formatblock", false, "h1");')

    def fbH2(self):
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("formatblock", false, "h2");')

    def fbH3(self):
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("formatblock", false, "h3");')

    def fbH4(self):
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("formatblock", false, "h4");')

    def fbH5(self):
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("formatblock", false, "h5");')

    def fbH6(self):
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("formatblock", false, "h6");')

    def fbBq(self):
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("formatblock", false, "blockquote");')

    def fbPre(self):
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("formatblock", false, "pre");')

    def fbAddr(self):
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("formatblock", false,"address");')

#### 字體大小 ####

    @pyqtSlot(int)
    def _fontsizeChanged(self, index):
        if index == 0:
            pass
        else:
            self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("fontsize", false,"{0}");'.format(index))
        self.fontsize.setCurrentIndex(0)
        self.noteArea.setFocus()

#### 縮進與對齊 ####

    @pyqtSlot()
    def _justifyLeft(self):
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("JustifyLeft");')

    @pyqtSlot()
    def _justifyCenter(self):
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("JustifyCenter");')

    @pyqtSlot()
    def _justifyRight(self):
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("JustifyRight");')

    @pyqtSlot()
    def _indent(self):
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("Indent");')

    @pyqtSlot()
    def _outdent(self):
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("Outdent");')

#### 列表 ####

    @pyqtSlot()
    def _ordlist(self):
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("InsertOrderedList");')

    @pyqtSlot()
    def _bullist(self):
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("InsertUnorderedList");')

#### 顏色 ####

    @pyqtSlot()
    def _fgcolor(self):
        color = QColorDialog.getColor(QColor(), self, _('Please select a font color...'))
        if not color.isValid():
            return
        rgb = (color.red(), color.green(), color.blue())
        hexcolor = webutils.rgbToHTMLColor(rgb)
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("ForeColor",false,"{0}");'.format(hexcolor))

    @pyqtSlot()
    def _bgcolor(self):
        color = QColorDialog.getColor(QColor(), self, _('Please select a background color...'))
        if not color.isValid():
            return
        rgb = (color.red(), color.green(), color.blue())
        hexcolor = webutils.rgbToHTMLColor(rgb)
        self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("BackColor",false,"{0}");'.format(hexcolor))

#### 插入 ####

    @pyqtSlot(list)
    def _inimg(self, imgList = []):
        '''插入圖片'''
        def preview(imagePath):
            pix = QPixmap(imagePath)
            if not pix.isNull():
                if pix.width() > 500 or pix.height() > 500: #只縮小不放大
                    pix = pix.scaled(QSize(500,500), Qt.KeepAspectRatio, Qt.SmoothTransformation)
            perviewLabel.setPixmap(pix)

        #如果imgList為空，讓使用者選擇
        if not imgList:
            #建立檔案選擇窗口
            dialog = QFileDialog(self, caption = _('Insert some images...'))
            perviewLabel = QLabel(dialog)
            dialog.currentChanged.connect(preview)
            dialog.setNameFilter(_("Image (*.png *.gif *.jpg *.jpeg *.svg *.bmp)"))
            dialog.setFileMode(QFileDialog.ExistingFiles)
            dialog.layout().addWidget(perviewLabel,1,3)
            dialog.setViewMode(QFileDialog.Detail)
            dialog.setMinimumSize(QSize(800,500))
            dialog.layout().setSizeConstraint(QLayout.SetMinimumSize)
            if dialog.exec():
                imgList = dialog.selectedFiles()
                if imgList == []:
                    return
            else:
                return

        #imgList中確定有值了

        #複製檔案並以相對路徑寫入
        #先解算來源與目標路徑
        for imgFile in imgList:
            fileName = os.path.split(imgFile)[1]
            fulltPath = os.path.join(self.pageemb, fileName)
            #有相同檔名的檔案時，變更目標檔名再拷貝。
            if os.path.isfile(fulltPath): #確認沒有相同檔名的檔案
                counter = 1
                name, ext = os.path.splitext(fileName)
                while True:
                    newName = name + '_' + str(counter) + ext
                    tmptPath = os.path.join(self.pageemb, newName)
                    if not os.path.isfile(tmptPath):
                        fulltPath = tmptPath
                        fileName = newName
                        break
                    counter += 1
            #拷貝
            shuext.copy(imgFile, fulltPath)

            #如果插入時要縮放的話……
            if self.IMG_FORMAT_INSERTRESIZE_ENABLE:
                img = QImage(fulltPath)
                if img.width() > self.IMG_FORMAT_WIDTH: #縮小不放大
                    img.scaled(QSize(self.IMG_FORMAT_WIDTH,1), Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation).save(fulltPath, quality = self.IMG_FORMAT_INSERTRESIZE_QUALITY)

            #替換格式字段中的變數{$FILENAME}、{$FILEPATH}與{$IMAGE}
            finalInsertString = self.IMG_FORMAT.replace('{$IMAGE}', '<img src="{$FILEPATH}" width={$PIC_WIDTH}px defaultwidth={$PIC_WIDTH}>')
            finalInsertString = finalInsertString.replace('{$FILENAME}', fileName)
            finalInsertString = finalInsertString.replace('{$FILEPATH}', fileName) # FILEPATH僅供向前相容用，或許很快就會移除。
            #如果圖片大小比寬度限制大，則套用寬度限制，否則使用原圖寬度
            imgWidth = QImage(fulltPath).width()
            if(imgWidth > self.IMG_FORMAT_WIDTH):
                finalInsertString = finalInsertString.replace('{$PIC_WIDTH}', str(self.IMG_FORMAT_WIDTH))
            else:
                finalInsertString = finalInsertString.replace('{$PIC_WIDTH}', str(imgWidth))

            #將內容插入頁面
            self.noteArea.page().mainFrame().evaluateJavaScript('''
                    var st = '{0}';
                    document.execCommand("insertHTML", false, st);'''.format(finalInsertString))

        #插入全部完成後執行一次JS，確保頁面更新。
        self.noteArea.page().mainFrame().evaluateJavaScript(js.imgSizeControl)

    def _inhtml(self):
        '''插入原始碼'''
        source = QInputDialog.getText(
                self,
                _('Insert HTML source code'),
                _('Insert HTML cource code on your cursor position.\nThis may be a simple hyperlink, or a youtube video.'),
                mode = QLineEdit.Normal,
                text = '<hr>')
        if source[0] == '':
            return
        self.noteArea.page().mainFrame().evaluateJavaScript('''
                        var st = '{0}';
                        document.execCommand("insertHTML", false, st);'''.format(source[0]))
        self.noteArea.page().action(QWebPage.Reload)

    def _indate(self):
        '''插入當前日期'''
        self.noteArea.page().mainFrame().evaluateJavaScript('''
                        var st = '{0}';
                        document.execCommand("insertHTML", false, st);'''.format(time.strftime(self.DATE_FORMAT)))

    def _intime(self):
        '''插入當前時間'''
        self.noteArea.page().mainFrame().evaluateJavaScript('''
                        var st = '{0}';
                        document.execCommand("insertHTML", false, st);'''.format(time.strftime(self.TIME_FORMAT)))

    def _link(self):
        '''建立超連結'''
        selText = self.noteArea.page().selectedText()
        if selText == '': #沒有選擇文字則跳出
            return
        #切割字串，[0]為顯示(showname)，[1]為路徑(linkname)
        spliter = '@'
        strlist = selText.rsplit(spliter, 1)

        #確認showname與linkName
        showname = None
        linkname = None
        if len(strlist) == 2: #能切割成兩個值
            if strlist[0] != '' and strlist[1] != '': #showname與linkname都有效
                showname = strlist[0]
                linkname = strlist[1]
            else: #既然有兩個變數，兩個變數都要有值，不然就回去！
                return
        else: #只有一個有效值的狀況，既當linkname又當showname
            if strlist[0] != '': #linkname不等於空
                showname = strlist[0]
                linkname = strlist[0]
            else: #linkname也為空，無任何意義，放棄！
                return

        #如果scheme為空，則表示沒有指定協定，嘗試建立lowiki連結
        if QUrl(linkname).scheme() == '' and self.dbcur:

            def insertLowikiLink(uuid, name):
                '''插入Lowiki連結'''
                url = QUrl()
                url.setScheme('lowiki')
                url.setPath(uuid)
                insStr = '<a href={0}>{1}</a>'.format(url.toString(), name)
                self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("insertHTML", false, "{0}");'.format(insStr))

            def createMenu(items):
                '''輸入一組item list
                item格式為：(uuid, contentplain, parentid, name)
                依照順序建立起一個QMenu並回傳'''
                menu = QMenu()
                for item in items:
                    #拼裝提示字串
                    getlen = 50 #參考文字長度
                    #拼裝節點名稱
                    actstr = _('➥Link to [ {0} ]: ').format(item[3])
                    #追加參考字並判斷末端要不要刪節號
                    if len(item[1]) <= getlen:
                        actstr += item[1]
                    else:
                        actstr += item[1][:getlen] + _('...')
                    #追加parent name
                    pName = '' #parent name
                    if item[2]:
                        pName = self.dbcur.execute('select name from notes where id = ?', (item[2],)).fetchone()[0]
                    else:
                        pName = _('Cover page')
                    actstr += ' ' + _('(') + pName + _(')')

                    #建立Action
                    act = QAction(actstr, menu) #擷取前50字作為索引
                    act.triggered.connect(functools.partial(insertLowikiLink, item[0], showname))
                    menu.addAction(act)
                return menu

            #進行精確搜尋
            items = self.dbcur.execute('select uuid, contentplain, parentid, name from notes where name = ?', (linkname,)).fetchall()
            #進行模糊比對
            fuzzyItems = self.dbcur.execute('select uuid,contentplain, parentid, name from notes where name like ? and name <> ?', ('%' + linkname + '%', linkname)).fetchall()

            #建立連結或彈出選單
            if len(items) == 0: #沒有任何精確對應的筆記頁
                #即使只有一個也要列出選單讓使用者確認，因為這是模糊的
                if len(fuzzyItems) >= 1:
                    menu = createMenu(fuzzyItems)
                    menu.exec_(QCursor.pos())
            elif len(items) == 1: #剛好有一個精確對應的筆記頁
                insertLowikiLink(items[0][0], showname)
            else: #有兩個以上可以精確對應的筆記頁
                menu = createMenu(items + fuzzyItems)
                menu.exec_(QCursor.pos())

        else: #如果scheme不為空，則用普通方式建立連結
            insStr = '<a href={0}>{1}</a>'.format(linkname, showname)
            self.noteArea.page().mainFrame().evaluateJavaScript('document.execCommand("insertHTML", false, "{0}");'.format(insStr))

    def _intemplate(self):
        '''插入模版'''

        #預定義函式
        def locate(pattern, root=os.getcwd()):
            '''Locate all files matching supplied filename pattern in and below
            supplied root directory.'''
            for path, dirs, files in os.walk(os.path.abspath(root)):
                for filename in fnmatch.filter(files, pattern):
                    yield os.path.join(path, filename)

        #預定義物件

        class TemplateLineEdit(QLineEdit):
            '''Template輸入專用的LineEdit'''

            def __init__(self, parent = None):
                super().__init__(parent)
                self.textEdited.connect(self.styleNone)

            def focusInEvent(self, e):
                '''焦點進入時全選'''
                super().focusInEvent(e)
                self.selectAll()

            def styleNone(self, text = ''):
                '''CSS重設為無'''
                self.setStyleSheet('')

            def styleError(self):
                '''CSS設為錯誤'''
                self.setStyleSheet('QLineEdit{background-color: pink;}')

            def styleRequired(self):
                '''CSS設為需要'''
                self.setStyleSheet('QLineEdit{background-color: #f0e68c;}')

        class TemplateDialog(QDialog):
            '''插入Template用的對話盒'''

            def __init__(self, parent = None):
                '''初始化'''
                #基本初始化
                super().__init__(parent)
                self.parent = parent
                self.__initData()
                self.__initLayout()

                #讀取預設模版
                try:
                    with open(self.psiPath, encoding = 'utf-8') as File:
                        name = File.read()
                except IOError: #如果檔案不存在……
                    name = ''

                #更新模版檔列表
                self.refreshTemplateDir(name)

            def __initData(self):
                '''準備初始化資料'''
                #全部模版檔案的路徑與識別名
                self.tempFilesDisplay = []
                self.tempFilesPath = []
                #當前選擇模版檔案的資訊
                self.templateString = '' #當前選擇的template的原始全文
                self.vTable = None #變數的資料
                self.editor = {} #以變數名為key，儲存對應的lineEdit的字典
                self.currentIndex = None #當前的選擇的template名字

                #舊有變數檔路徑
                self.VRPath = os.path.join(
                        templateDir, '.VariableRecord')
                #pre-selected-item檔案的路徑(內存上次選擇的模版)
                self.psiPath = os.path.join(templateDir,
                        '.PreSelectedItem')

                #變數分析器
                self.reobj = re.compile(r'\$\{(\*?)(\d*)(.*?)(?:|::(.*?))\}')
                #語法舉例：${*變數名::提示字}
                #Findall出來的item:
                # 1.為*或空字串（代表必要或否）
                # 2.可以沒有的排序數字
                # 3.為變數名稱
                # 4.為提示字（可不用）

            def __initLayout(self):
                '''初始化Layout'''
                #拼裝對話窗
                self.mainLayout = QFormLayout()
                #ComboBox設定
                self.templateSelectBox = QComboBox()
                self.templateSelectBox.currentIndexChanged.connect(self.comboIndexChanged)
                #按鈕設定
                self.confirmButton = QPushButton(_('Confirm'))
                self.confirmButton.pressed.connect(self.confirm)
                self.confirmButton.setDefault(True)
                self.cancelButton = QPushButton(_('Cancel'))
                self.cancelButton.pressed.connect(self.cancel)
                self.openDirButton = QPushButton(_('Open'))
                self.openDirButton.pressed.connect(self.openDir)
                #按鈕列layout
                buttonLayout = QHBoxLayout()
                buttonLayout.addStretch()
                buttonLayout.addWidget(self.openDirButton)
                buttonLayout.addWidget(self.cancelButton)
                buttonLayout.addWidget(self.confirmButton)
                #主介面鋪設
                mainLabel = QLabel(_('Insert a <b>"template"</b> on your cursor position.<br><br>You can create your own template in LoNote\'s template folder.'))
                mainLabel.setOpenExternalLinks(True)
                self.mainLayout.addRow(mainLabel)
                self.mainLayout.addRow(self.templateSelectBox)
                self.mainLayout.addRow(buttonLayout)
                self.dataInputWidget = QWidget()
                self.mainLayout.addRow(self.dataInputWidget)
                #對話窗口框架
                self.setWindowTitle(_('Select a template you want'))
                self.setLayout(self.mainLayout)

            def hideEvent(self, e):
                '''讓dialog隱藏前先儲存'''
                #儲存當前模版變數
                index = self.templateSelectBox.currentIndex()
                self.saveVariable(index)
                #儲存預選模版是哪一個
                with open(self.psiPath, mode = 'w', encoding = 'utf-8') as File:
                    File.write(self.tempFilesDisplay[index])
                super().hideEvent(e)

            def refreshTemplateDir(self, name = ''):
                '''找出所有指定路徑下的模版檔案(.html),並記錄他們
                   name是指定要將comboBox選擇在哪一個位置上用的'''
                for file in locate('*.html' ,root = templateDir):
                    self.tempFilesDisplay.append(
                            os.path.splitext(
                                os.path.relpath(file, templateDir))[0])
                    self.tempFilesPath.append(file)
                #用模版檔案列表更新templateBox
                self.templateSelectBox.clear()
                self.templateSelectBox.addItems(self.tempFilesDisplay)
                try: #當前 index 的值由 name 決定
                    index = self.tempFilesDisplay.index(name)
                except:
                    index = 0
                self.templateSelectBox.setCurrentIndex(index)
                #產生初始變數列表
                data = self.parsingFileByIndex(index)
                self.vTable = data
                self.renewInputFrame(data)

            def comboIndexChanged(self, index):
                '''當comboBox改變時要做的事'''
                #先儲存之前的變數
                self.saveVariable(self.currentIndex)
                self.currentIndex = index
                #分析變數
                data = self.parsingFileByIndex(index)
                self.vTable = data
                #變更佈局
                self.renewInputFrame(data)

            def renewInputFrame(self, data):
                '''藉parsing File後的Data產生輸入框架'''
                #新的Layout
                dataInputLayout = QFormLayout()
                dataInputLayout.setLabelAlignment(Qt.AlignRight) #右對齊
                #準備新的editor記錄表
                editorDict = {}
                #產生輸入面板
                #先排序
                sortedName = [name for index, name in sorted([(data[name]['sortindex'], name) for name in data])]
                for name in sortedName:
                    item = data[name]
                    #初始化輸入欄
                    editorDict[name] = TemplateLineEdit()
                    editor = editorDict[name]
                    #調整輸入欄位
                    editor.setText(item['value'])
                    editor.setPlaceholderText(item['description'])
                    editor.setToolTip(item['description'])
                    #設定Label
                    if item['required']:
                        showName = '*<b>{}</b>'.format(name)
                        editor.styleRequired()
                    else:
                        showName = '{}'.format(name)
                    label = QLabel(showName)
                    label.setAlignment(Qt.AlignJustify)
                    #將Label和lineedit放上面板
                    dataInputLayout.addRow(label, editor)
                #回收並更新原本的Layout
                self.mainLayout.removeWidget(self.dataInputWidget)
                self.dataInputWidget.hide()
                self.dataInputWidget.close()
                self.dataInputWidget = QWidget() #dataInputWidget更新
                #連結輸入框架
                self.dataInputWidget.setLayout(dataInputLayout)
                self.mainLayout.addRow(self.dataInputWidget)
                #設定self.editor
                self.editor = editorDict
                #重設面板大小
                self.adjustSize()

            def parsingFileByIndex(self, index):
                '''解析檔案內容並產生輸入框架'''
                filePath = self.tempFilesPath[index]
                data = {}
                #讀取目前的檔案
                with open(filePath, encoding='utf-8') as File:
                    self.templateString = File.read()
                #讀出曾經輸入過的舊變數資料
                recordDict = self.loadVariable(index)
                #分析變數並記錄
                #引數的排序和self.reobj的結構有關，請注意
                for requiredStar, sortIndexStr, name, desc in\
                        self.reobj.findall(self.templateString):
                    #將輸入欄的位置與「必要或否」記錄下來，之後才能回收輸入資料
                    #因為記錄可能不存在
                    if name not in recordDict:
                        value = ''
                    else:
                        value = recordDict[name]
                    #替資料進行預處理
                    required = bool(requiredStar)
                    if sortIndexStr:
                        sortIndex = int(sortIndexStr)
                    else:
                        sortIndex = 10000 #未排序者設為10000

                    #因為記錄可能在先前的parsing中已經被登錄過了
                    #這邊採用先登錄先贏的策略（空值除外）
                    if name not in data: #表示第一次記錄本變數，錄入全部
                        data[name] = {
                                'sortindex' :sortIndex,
                                'required' :bool(requiredStar),
                                'description' :desc,
                                'value' :value}
                    else: #如果不是第一次記入，則空值處嘗試錄入新值
                        if data[name]['sortindex'] == 10000: #10000表未設值
                            data[name]['sortindex'] = sortIndex
                        if not data[name]['required']:
                            data[name]['required'] = bool(requiredStar)
                        if not data[name]['description']:
                            data[name]['description'] = desc
                        if not data[name]['value']:
                            data[name]['value'] = value
                return data

            def cancel(self):
                '''取消'''
                dialog.hide()

            def confirm(self):
                '''確認套用本模版'''

                def repl(matchobj):
                    '''定義替換方式'''
                    #注意，下面括號中的值和self.reobj的結構有關
                    name = matchobj.group(3)
                    #用對應名字的editor的text來替換
                    return self.editor[name].text()

                #檢查是否所有必要欄位都有輸入
                invalid = False
                for name in self.editor:
                    if self.vTable[name]['required']: #必要欄位
                        #空字串則不行，顯示警告色並標記無效旗標
                        if not bool(self.editor[name].text()):
                            self.editor[name].styleError()
                            invalid = True
                        else:
                            self.editor[name].styleNone()
                if invalid == True:
                    return

                #檢查通過，進行插入
                #記錄本次輸入的變數
                index = self.templateSelectBox.currentIndex()
                self.saveVariable(index)
                #替換變數到模版中
                insertString = self.reobj.sub(repl, self.templateString)
                #因為用js插入，要處理js多行問題。在每行末端加入java換行記號
                insertString = insertString.replace('\n','\\\n')
                #具體插入
                self.parent.noteArea.page().mainFrame().evaluateJavaScript('''
                                var st = '{0}';
                                document.execCommand("insertHTML", false, st);'''.format(insertString))
                #關閉面板
                self.close()

            def openDir(self):
                '''打開模版資料夾'''
                QDesktopServices.openUrl(QUrl.fromLocalFile(templateDir))

            def saveVariable(self, index):
                '''儲存當前清單中的變數'''
                if index is None:
                    return
                recordDict = {} #用來記錄變數
                for name in self.editor:
                    recordDict[name] = self.editor[name].text()
                keyName = self.tempFilesDisplay[index]
                VRecord = shelve.open(self.VRPath, protocol = 3)
                VRecord[keyName] = recordDict
                VRecord.close()

            def loadVariable(self, index):
                '''讀取指定的變數字典'''
                keyName = self.tempFilesDisplay[index]
                VRecord = shelve.open(
                        filename = self.VRPath, protocol = 3)
                try:
                    recordDict = VRecord[keyName]
                except:
                    recordDict = {}
                VRecord.close()
                return recordDict

        dialog = TemplateDialog(self)
        dialog.exec()
